package cn.com.anysdk.redis.factory;

import cn.com.anysdk.redis.config.AliyunRedisConfig;
import cn.com.anysdk.redis.config.RedisConfigProperties;
import cn.com.anysdk.redis.event.RedisConnectionEvent;
import cn.com.anysdk.redis.event.RedisEnvironmentChangeEvent;
import cn.com.anysdk.redis.event.RedisEventManager;
import cn.com.anysdk.redis.exception.AliyunRedisException;
import cn.com.anysdk.redis.exception.RedisConnectionException;
import cn.com.anysdk.redis.exception.RedisEnvironmentException;
import cn.com.anysdk.redis.exception.RedisExceptionHandler;
import cn.com.anysdk.redis.reconnect.ExponentialBackoffReconnectStrategy;
import cn.com.anysdk.redis.reconnect.RedisReconnectManager;
import cn.com.anysdk.redis.reconnect.RedisReconnectStrategy;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Redis连接工厂
 * 负责创建和管理Redis连接，支持本地和阿里云环境
 */
@Slf4j
public class RedisConnectionFactory {
    private final RedisConfigProperties properties;
    private final RedisEventManager eventManager;
    private final RedisExceptionHandler exceptionHandler;
    private final RedisReconnectManager reconnectManager;

    private JedisPool jedisPool;
    private JedisCluster jedisCluster;
    private JedisSentinelPool jedisSentinelPool;
    private final AtomicBoolean initialized = new AtomicBoolean(false);

    /**
     * 创建Redis连接工厂
     *
     * @param properties Redis配置属性
     * @param eventManager 事件管理器
     */
    public RedisConnectionFactory(RedisConfigProperties properties, RedisEventManager eventManager) {
        this.properties = properties;
        this.eventManager = eventManager;
        this.exceptionHandler = new RedisExceptionHandler(eventManager);

        // 创建重连策略和管理器
        RedisReconnectStrategy reconnectStrategy = new ExponentialBackoffReconnectStrategy(
                1000, // 初始等待时间1秒
                30000, // 最大等待时间30秒
                2.0, // 指数增长倍数
                10 // 最大重试次数
        );
        this.reconnectManager = new RedisReconnectManager(eventManager, reconnectStrategy);

        initConnection();
    }

    /**
     * 初始化Redis连接
     */
    public void initConnection() {
        try {
            JedisPoolConfig poolConfig = createPoolConfig();

            if ("local".equals(properties.getEnvironment())) {
                initLocalConnection(poolConfig);
            } else if ("aliyun".equals(properties.getEnvironment())) {
                initAliyunConnection(poolConfig);
            } else {
                throw new RedisEnvironmentException.UndefinedEnvironmentException(
                        "未定义的Redis环境: " + properties.getEnvironment());
            }

            initialized.set(true);

            // 发布连接成功事件
            fireConnectionEvent(true);

            log.info("Redis连接初始化成功，当前环境: {}", properties.getEnvironment());
        } catch (Exception e) {
            log.error("Redis连接初始化失败", e);
            try {
                exceptionHandler.handleException(e, this, properties.getEnvironment());
            } catch (Exception ex) {
                // 启动自动重连
                reconnectManager.startReconnect(this, properties.getEnvironment(), this::reconnect);
            }
        }
    }

    /**
     * 创建连接池配置
     *
     * @return 连接池配置
     */
    private JedisPoolConfig createPoolConfig() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        RedisConfigProperties.PoolConfig pool = "local".equals(properties.getEnvironment())
                ? properties.getLocal().getPool()
                : properties.getAliyun().getPool();

        poolConfig.setMaxTotal(pool.getMaxTotal());
        poolConfig.setMaxIdle(pool.getMaxIdle());
        poolConfig.setMinIdle(pool.getMinIdle());
        poolConfig.setMaxWaitMillis(pool.getMaxWaitMillis());

        // 强制启用连接验证，解决启动时连接问题
        poolConfig.setTestOnBorrow(true);  // 获取连接时验证
        poolConfig.setTestOnReturn(true);  // 归还连接时验证
        poolConfig.setTestWhileIdle(true); // 空闲时验证

        // 优化验证参数
        poolConfig.setTimeBetweenEvictionRunsMillis(30000L); // 30秒检查一次
        poolConfig.setNumTestsPerEvictionRun(3);
        poolConfig.setMinEvictableIdleTimeMillis(60000L); // 1分钟后可被清理

        // 添加额外的稳定性配置
        poolConfig.setBlockWhenExhausted(true); // 连接池耗尽时阻塞等待
        poolConfig.setJmxEnabled(false); // 禁用JMX避免潜在问题

        return poolConfig;
    }

    private void initLocalConnection(JedisPoolConfig poolConfig) {
        closeAllConnections();

        RedisConfigProperties.LocalConfig local = properties.getLocal();

        // 根据密码是否为空选择不同的构造方法
        if (local.getPassword() == null || local.getPassword().trim().isEmpty() || "null".equals(local.getPassword())) {
            jedisPool = new JedisPool(
                    poolConfig,
                    local.getHost(),
                    local.getPort(),
                    local.getTimeout().getConnectionTimeout(),
                    null,  // 密码为null
                    local.getDatabase()
            );
        } else {
            jedisPool = new JedisPool(
                    poolConfig,
                    local.getHost(),
                    local.getPort(),
                    local.getTimeout().getConnectionTimeout(),
                    local.getPassword(),
                    local.getDatabase()
            );
        }

        // 连接预热
        if (local.getPool().isPreWarm()) {
            preWarmConnections(local.getPool().getMinIdle());
        }
    }

    /**
     * 初始化阿里云Redis连接
     *
     * @param poolConfig 连接池配置
     */

private void initAliyunConnection(JedisPoolConfig poolConfig) {
    // 本地测试时跳过阿里云配置
    if ("local".equals(properties.getEnvironment())) {
        return;
    }

    closeAllConnections();
    RedisConfigProperties.AliyunConfig aliyun = properties.getAliyun();

    // 本地测试时使用标准模式
    jedisPool = new JedisPool(
            poolConfig,
            "localhost",
            6379,
            aliyun.getTimeout().getConnectionTimeout(),
            null,
            0
    );
}

     /**
     * 初始化阿里云集群版Redis连接
     */
//    private void initAliyunClusterConnection(JedisPoolConfig poolConfig,
//                                            RedisConfigProperties.AliyunConfig aliyun,
//                                            AliyunRedisConfig aliyunConfig) {
//        String[] clusterNodes = aliyunConfig.getClusterNodes();
//        if (clusterNodes == null || clusterNodes.length == 0) {
//            throw new AliyunRedisException("阿里云Redis集群节点配置缺失");
//        }
//
//        Set<HostAndPort> nodes = new HashSet<>();
//        for (String node : clusterNodes) {
//            String[] parts = node.split(":");
//            if (parts.length != 2) {
//                throw new AliyunRedisException("无效的集群节点格式: " + node);
//            }
//            nodes.add(new HostAndPort(parts[0], Integer.parseInt(parts[1])));
//        }
//
//        jedisCluster = new JedisCluster(
//                nodes,
//                aliyun.getTimeout().getConnectionTimeout(),
//                aliyun.getTimeout().getSoTimeout(),
//                aliyun.getTimeout().getMaxAttempts(),
//                aliyunConfig.getPassword(),
//                poolConfig
//        );
//    }

     /**
     * 初始化阿里云哨兵版Redis连接
     */
    private void initAliyunSentinelConnection(JedisPoolConfig poolConfig,
                                             RedisConfigProperties.AliyunConfig aliyun,
                                             AliyunRedisConfig aliyunConfig) {
        String[] sentinelNodes = aliyunConfig.getSentinelNodes();
        if (sentinelNodes == null || sentinelNodes.length == 0) {
            throw new AliyunRedisException("阿里云Redis哨兵节点配置缺失");
        }

        if (aliyunConfig.getMasterName() == null || aliyunConfig.getMasterName().isEmpty()) {
            throw new AliyunRedisException("阿里云Redis哨兵主节点名称配置缺失");
        }

        // 使用 Arrays.asList 转换为 List，然后创建 HashSet
        Set<String> sentinels = new HashSet<>(Arrays.asList(sentinelNodes));

        jedisSentinelPool = new JedisSentinelPool(
                aliyunConfig.getMasterName(),
                sentinels,
                poolConfig,
                aliyun.getTimeout().getConnectionTimeout(),
                aliyunConfig.getPassword(),
                aliyunConfig.getDatabase()
        );
    }

   /**
 * 连接预热
 *
 * @param minConnections 最小连接数
 */
private void preWarmConnections(int minConnections) {
    log.info("预热Redis连接池，创建{}个初始连接", minConnections);
    Set<Jedis> connections = new HashSet<>();

    try {
        for (int i = 0; i < minConnections; i++) {
            Jedis jedis = getConnection();
            // 测试连接
            String response = jedis.ping();
            if (!"PONG".equals(response)) {
                throw new RedisConnectionException("Redis连接测试失败，返回: " + response);
            }
            connections.add(jedis);
        }
    } finally {
        // 归还连接到连接池
        for (Jedis jedis : connections) {
            jedis.close();
        }
    }
}

    /**
     * 获取Redis连接
     *
     * @return Jedis连接实例
     */
    public Jedis getConnection() {
        if (!initialized.get()) {
            throw new RedisConnectionException("Redis连接尚未初始化");
        }

        return getConnectionWithRetry(3);
    }

    /**
     * 带重试机制的获取连接
     *
     * @param maxRetries 最大重试次数
     * @return Jedis连接实例
     */
    private Jedis getConnectionWithRetry(int maxRetries) {
        Exception lastException = null;

        for (int attempt = 1; attempt <= maxRetries; attempt++) {
            try {
                Jedis jedis = jedisPool.getResource();

                // 验证连接健康性
                if (isConnectionHealthy(jedis)) {
                    return jedis;
                } else {
                    // 连接不健康，关闭并重试
                    log.warn("获取到不健康的Redis连接，尝试重试 ({}/{})", attempt, maxRetries);
                    try {
                        jedis.close();
                    } catch (Exception e) {
                        log.debug("关闭不健康连接时出错", e);
                    }

                    if (attempt < maxRetries) {
                        // 短暂等待后重试
                        try {
                            Thread.sleep(100 * attempt); // 递增等待时间
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                            throw new RedisConnectionException("获取连接被中断", e);
                        }
                    }
                }
            } catch (Exception e) {
                lastException = e;
                log.warn("获取Redis连接失败，尝试重试 ({}/{}): {}", attempt, maxRetries, e.getMessage());

                if (attempt < maxRetries) {
                    // 短暂等待后重试
                    try {
                        Thread.sleep(100 * attempt); // 递增等待时间
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new RedisConnectionException("获取连接被中断", ie);
                    }
                }
            }
        }

        // 所有重试都失败了
        log.error("获取Redis连接失败，已重试{}次", maxRetries);
        try {
            exceptionHandler.handleException(lastException, this, properties.getEnvironment());
        } catch (Exception ex) {
            reconnectManager.startReconnect(this, properties.getEnvironment(), this::reconnect);
        }
        throw new RedisConnectionException("获取Redis连接失败: " +
            (lastException != null ? lastException.getMessage() : "未知错误"), lastException);
    }

    /**
     * 检查连接健康性
     *
     * @param jedis Redis连接
     * @return 连接是否健康
     */
    private boolean isConnectionHealthy(Jedis jedis) {
        if (jedis == null) {
            return false;
        }

        try {
            // 使用ping命令检查连接
            String response = jedis.ping();
            return "PONG".equals(response);
        } catch (Exception e) {
            log.debug("连接健康检查失败: {}", e.getMessage());
            return false;
        }
    }

    /**
     * 获取Redis集群连接
     *
     * @return JedisCluster实例
     */
    public JedisCluster getClusterConnection() {
        if (!initialized.get()) {
            throw new RedisConnectionException("Redis连接尚未初始化");
        }

        if (jedisCluster == null) {
            throw new RedisConnectionException("Redis集群连接不可用，当前模式不是集群模式");
        }

        return jedisCluster;
    }

    /**
     * 重连Redis
     *
     * @return 重连是否成功
     */
    private boolean reconnect() {
        try {
            log.info("尝试重新连接Redis...");
            initConnection();
            return initialized.get();
        } catch (Exception e) {
            log.error("Redis重连失败", e);
            return false;
        }
    }

    /**
     * 关闭所有连接
     */
    private void closeAllConnections() {
        if (jedisPool != null) {
            jedisPool.close();
            jedisPool = null;
        }

        if (jedisCluster != null) {
            try {
                jedisCluster.close();
            } catch (Exception e) {
                log.error("关闭Redis集群连接失败", e);
            }
            jedisCluster = null;
        }

        if (jedisSentinelPool != null) {
            jedisSentinelPool.close();
            jedisSentinelPool = null;
        }

        // 发布断开连接事件
        if (initialized.getAndSet(false)) {
            fireConnectionEvent(false);
        }
    }

    /**
     * 关闭连接工厂
     */
    public void close() {
        reconnectManager.stopReconnect();
        closeAllConnections();
        reconnectManager.shutdown();
    }

    /**
     * 切换Redis环境
     *
     * @param environment 目标环境（"local"或"aliyun"）
     */
    public void switchEnvironment(String environment) {
        if (environment == null || environment.isEmpty()) {
            throw new RedisEnvironmentException("环境名称不能为空");
        }

        if (!"local".equals(environment) && !"aliyun".equals(environment)) {
            throw new RedisEnvironmentException.UndefinedEnvironmentException(
                    "未定义的Redis环境: " + environment);
        }

        if (environment.equals(properties.getEnvironment())) {
            log.info("当前已经是{}环境，无需切换", environment);
            return;
        }

        String oldEnvironment = properties.getEnvironment();

        try {
            // 停止重连任务
            reconnectManager.stopReconnect();

            // 关闭现有连接
            closeAllConnections();

            // 更新环境配置
            properties.setEnvironment(environment);

            // 重新初始化连接
            initConnection();

            // 发布环境切换事件
            eventManager.fireEnvironmentChangeEvent(new RedisEnvironmentChangeEvent(
                    this,
                    environment,
                    oldEnvironment,
                    environment,
                    new Date()
            ));

            log.info("Redis环境已从{}切换到{}", oldEnvironment, environment);
        } catch (Exception e) {
            // 切换失败，恢复原环境
            properties.setEnvironment(oldEnvironment);
            log.error("Redis环境切换失败", e);

            try {
                exceptionHandler.handleException(
                        new RedisEnvironmentException.SwitchException("环境切换失败: " + e.getMessage(), e),
                        this,
                        oldEnvironment
                );
            } catch (Exception ex) {
                // 尝试重新连接原环境
                reconnectManager.startReconnect(this, oldEnvironment, this::reconnect);
            }
        }
    }

    /**
     * 发布连接事件
     *
     * @param connected 是否已连接
     */
    private void fireConnectionEvent(boolean connected) {
        String host = properties.getLocal().getHost();
        int port = properties.getLocal().getPort();

        RedisConnectionEvent event = new RedisConnectionEvent(
                this,
                properties.getEnvironment(),
                host,
                port,
                new Date()
        );

        if (connected) {
            eventManager.fireConnectEvent(event);
        } else {
            eventManager.fireDisconnectEvent(event);
        }
    }
}
